﻿//------------------------------------------------------------------------------
// Copyright © FRA & FV 2014
// All rights reserved
//------------------------------------------------------------------------------
// Binary Serialization Custom Tool
//
// SVN revision information:
//   $Revision: 100 $
//------------------------------------------------------------------------------
using System;
using System.CodeDom;
using System.Linq;
using FRAFV.Binary.Serialization.CodeDom;

namespace FRAFV.Binary.Serialization
{
	public static class FormatManager
	{
		#region Constants

		public static readonly string[] ReservedNames = new string[]
		{
			"reader", "length", "writer", "encoding", "encoder", "count", "index", "buffer", "enumerator", "item"
		};

		#endregion

		#region String extensions

		public static bool IsNullEmpty(this string value)
		{
			return String.IsNullOrWhiteSpace(value);
		}

		public static string NullEmpty(this string value)
		{
			return String.IsNullOrWhiteSpace(value) ? null : value;
		}

		#endregion

		#region Format conversion

		public static BinFormat ResolveFormat(BinFormat format, string type)
		{
			if (format != BinFormat.Auto) return format;
			if (type.StartsWith("System."))
				type = type.Substring("System.".Length);
			switch (type)
			{
				case "sbyte":
				case "SByte":
					return BinFormat.Int8;
				case "short":
				case "Int16":
					return BinFormat.Int16;
				case "int":
				case "Int32":
					return BinFormat.Int32;
				case "long":
				case "Int64":
					return BinFormat.Int64;
				case "byte":
				case "Byte":
					return BinFormat.UInt8;
				case "ushort":
				case "UInt16":
					return BinFormat.UInt16;
				case "uint":
				case "UInt32":
					return BinFormat.UInt32;
				case "ulong":
				case "UInt64":
					return BinFormat.UInt64;
				case "bool":
				case "Boolean":
					return BinFormat.Bit;
				case "float":
				case "Single":
					return BinFormat.Float;
				case "double":
				case "Double":
					return BinFormat.Double;
				case "byte[]":
				case "Byte[]":
					return BinFormat.Binary;
				case "string":
				case "String":
					return BinFormat.CString;
				case "char[]":
				case "Char[]":
					return BinFormat.PString;
				case "char":
				case "Char":
					return BinFormat.Char;
				default:
					throw new NotImplementedException("Unknown property type " + type);
			}
		}

		public static Type AsType(this BinFormat format)
		{
			switch (format)
			{
				case BinFormat.Int8:
					return typeof(sbyte);
				case BinFormat.Int16:
					return typeof(short);
				case BinFormat.Int24:
				case BinFormat.UInt24:
				case BinFormat.Int32:
					return typeof(int);
				case BinFormat.Int64:
					return typeof(long);
				case BinFormat.UInt8:
					return typeof(byte);
				case BinFormat.UInt16:
					return typeof(ushort);
				case BinFormat.UInt32:
					return typeof(uint);
				case BinFormat.UInt64:
					return typeof(ulong);
				case BinFormat.Bool: //int
				case BinFormat.Bit:
				case BinFormat.VariantBool: //short
					return typeof(bool);
				case BinFormat.Float:
					return typeof(float);
				case BinFormat.Double:
					return typeof(double);
				case BinFormat.Date: //long
				case BinFormat.UNIXDateI32: //int
				case BinFormat.UNIXDateU32: //uint
				case BinFormat.UNIXDate64: //long
				case BinFormat.MacDate32: //uint
				case BinFormat.MacDate64: //long
				case BinFormat.OLEDate: //double
				case BinFormat.FileTime: //long
					return typeof(DateTime);
				case BinFormat.Binary:
					return typeof(byte[]);
				case BinFormat.CString:
					return typeof(string);
				case BinFormat.PString:
					return typeof(char[]);
				case BinFormat.Char:
					return typeof(char);
				default:
					throw new NotImplementedException("Unknown property format " + format);
			}
		}

		public static string ResolveRead(this BinFormat format, out Type arg)
		{
			arg = null;
			switch (format)
			{
				case BinFormat.Int8:
					return "ReadSByte";
				case BinFormat.Int16:
				case BinFormat.VariantBool: //v != 0
					return "ReadInt16";
				case BinFormat.Int24:
					return "ReadInt24";
				case BinFormat.Int32:
				case BinFormat.Bool: //v != 0
					return "ReadInt32";
				case BinFormat.Int64:
				case BinFormat.Date: //new DateTime(v)
				case BinFormat.FileTime: //DateTime.FromFileTimeUtc(v)
					return "ReadInt64";
				case BinFormat.UInt8:
					return "ReadByte";
				case BinFormat.UInt16:
					return "ReadUInt16";
				case BinFormat.UInt24:
					return "ReadUInt24";
				case BinFormat.UInt32:
					return "ReadUInt32";
				case BinFormat.UInt64:
					return "ReadUInt64";
				case BinFormat.SynchSafeInt16:
					return "ReadSynchSafeInt16";
				case BinFormat.SynchSafeInt32:
					return "ReadSynchSafeInt32";
				case BinFormat.SynchSafeInt64:
					arg = typeof(int);
					return "ReadSynchSafeInt64";
				case BinFormat.Bit:
					return "ReadBoolean";
				case BinFormat.Float:
					return "ReadSingle";
				case BinFormat.Double:
				case BinFormat.OLEDate: //DateTime.FromOADate(v)
					return "ReadDouble";
				case BinFormat.UNIXDateI32:
					return "ReadUNIXDateI32";
				case BinFormat.UNIXDateU32:
					return "ReadUNIXDateU32";
				case BinFormat.UNIXDate64:
					return "ReadUNIXDate64";
				case BinFormat.MacDate32:
					return "ReadMacDate32";
				case BinFormat.MacDate64:
					return "ReadMacDate64";
				case BinFormat.Binary:
					arg = typeof(int);
					return "ReadBytes";
				case BinFormat.CString:
					return "ReadString";
				case BinFormat.PString:
					arg = typeof(int);
					return "ReadChars";
				case BinFormat.Char:
					return "ReadChar";
				default:
					throw new NotImplementedException("Unknown reading property format " + format);
			}
		}

		public static int AsSize(this BinFormat format)
		{
			switch (format)
			{
				case BinFormat.Int8:
				case BinFormat.UInt8:
				case BinFormat.Bit:
					return 1;
				case BinFormat.Int16:
				case BinFormat.UInt16:
				case BinFormat.SynchSafeInt16:
				case BinFormat.VariantBool:
					return 2;
				case BinFormat.Int24:
				case BinFormat.UInt24:
					return 3;
				case BinFormat.Int32:
				case BinFormat.UInt32:
				case BinFormat.SynchSafeInt32:
				case BinFormat.Bool:
				case BinFormat.Float:
				case BinFormat.UNIXDateI32:
				case BinFormat.UNIXDateU32:
				case BinFormat.MacDate32:
					return 4;
				case BinFormat.Int64:
				case BinFormat.UInt64:
				case BinFormat.SynchSafeInt64:
				case BinFormat.Double:
				case BinFormat.Date:
				case BinFormat.UNIXDate64:
				case BinFormat.MacDate64:
				case BinFormat.OLEDate:
				case BinFormat.FileTime:
					return 8;
				default:
					throw new NotImplementedException("Unknown calculating property format " + format);
			}
		}

		public static string ResolveWrite(this BinFormat format, out Type arg)
		{
			arg = null;
			switch (format)
			{
				case BinFormat.Int24:
				case BinFormat.UInt24:
					return "WriteInt24";
				case BinFormat.Int8:
				case BinFormat.Int16:
				case BinFormat.Int32:
				case BinFormat.Int64:
				case BinFormat.UInt8:
				case BinFormat.UInt16:
				case BinFormat.UInt32:
				case BinFormat.UInt64:
				case BinFormat.Bool: //v ? 1 : 0
				case BinFormat.Bit:
				case BinFormat.VariantBool: //v ? -1 : 0
				case BinFormat.Float:
				case BinFormat.Double:
				case BinFormat.Date: //v.Ticks
				case BinFormat.OLEDate: //v.ToOADate()
				case BinFormat.FileTime: //v.ToFileTimeUtc()
				case BinFormat.Binary:
				case BinFormat.CString:
				case BinFormat.PString: //v.ToCharArray()
				case BinFormat.Char:
					return "Write";
				case BinFormat.SynchSafeInt16:
				case BinFormat.SynchSafeInt32:
					return "WriteSynchSafe";
				case BinFormat.SynchSafeInt64:
					arg = typeof(int);
					return "WriteSynchSafe";
				case BinFormat.UNIXDateI32:
					return "WriteUNIXDateI32";
				case BinFormat.UNIXDateU32:
					return "WriteUNIXDateU32";
				case BinFormat.UNIXDate64:
					return "WriteUNIXDate64";
				case BinFormat.MacDate32:
					return "WriteMacDate32";
				case BinFormat.MacDate64:
					return "WriteMacDate64";
				default:
					throw new NotImplementedException("Unknown writing property format " + format);
			}
		}

		public static string ResolveBinaryReaderType(this BinBlockAttribute binBlock)
		{
			var builder = binBlock as ClassParser.BinBlockBuilder;
			if (builder != null) return builder.BinaryReaderTypeName;
			if (binBlock == null || binBlock.BinaryReaderType == null) return null;
			return binBlock.BinaryReaderType.FullName;
		}

		public static string ResolveBinaryWriterType(this BinBlockAttribute binBlock)
		{
			var builder = binBlock as ClassParser.BinBlockBuilder;
			if (builder != null) return builder.BinaryWriterTypeName;
			if (binBlock == null || binBlock.BinaryWriterType == null) return null;
			return binBlock.BinaryWriterType.FullName;
		}

		public static string ResolveItemType(this BinArrayItemAttribute binArrayItem)
		{
			var builder = binArrayItem as ClassParser.BinArrayItemBuilder;
			if (builder != null) return builder.ItemTypeName;
			if (binArrayItem == null || binArrayItem.ItemType == null) return null;
			return binArrayItem.ItemType.FullName;
		}

		public static string ResolveItemType(this BinItemAttribute binItem)
		{
			var builder = binItem as ClassParser.BinItemBuilder;
			if (builder != null) return builder.ItemTypeName;
			if (binItem == null || binItem.ItemType == null) return null;
			return binItem.ItemType.FullName;
		}

		#endregion

		#region Checkers

		public static bool HasLength(this BinFormat format)
		{
			switch (format)
			{
				case BinFormat.Binary:
				case BinFormat.PString:
					return true;
				default:
					return false;
			}
		}

		public static bool HasEncoding(this BinFormat format)
		{
			switch (format)
			{
				case BinFormat.CString:
				case BinFormat.PString:
				case BinFormat.Char:
					return true;
				default:
					return false;
			}
		}
		#endregion

		#region Code expressions

		public static CodeFieldReferenceExpression AsField(this string field)
		{
			return new CodeFieldReferenceExpression(
				ReservedNames.Contains(field) ? new CodeThisReferenceExpression() : null, field);
		}

		public static CodeBinaryOperatorExpression IsNull(this ClassParser.ClassProperty prop)
		{
			return prop.Name.AsField().IsNull();
		}

		public static CodeBinaryOperatorExpression IsNotNull(this ClassParser.ClassProperty prop)
		{
			return prop.Name.AsField().IsNotNull();
		}

		#endregion

		#region Code cast expressions

		public static CodeExpression Cast(this CodeExpression value, BinFormat fromType, string toType)
		{
			switch (fromType)
			{
				case BinFormat.Bool:
				case BinFormat.VariantBool:
					return value.Inequality(0).Cast(toType);
				case BinFormat.Date:
					return new CodeObjectCreateExpression(typeof(DateTime), value).Cast(toType);
				case BinFormat.OLEDate:
					return typeof(DateTime).MethodInvoke("FromOADate", value).Cast(toType);
				case BinFormat.FileTime:
					return typeof(DateTime).MethodInvoke("FromFileTimeUtc", value).Cast(toType);
				case BinFormat.PString:
					switch (toType)
					{
						case "string":
						case "String":
						case "System.String":
							return new CodeObjectCreateExpression(typeof(string), value);
						default:
							return value.Cast(toType);
					}
				default:
					return value.Cast(toType);
			}
		}

		public static CodeExpression Cast(this CodeExpression value, string fromType, BinFormat toType)
		{
			switch (toType)
			{
				case BinFormat.Bool:
					return value.Condition(1, 0);
				case BinFormat.VariantBool:
					return value.Condition(-1, 0);
				case BinFormat.Date:
					return value.Cast<DateTime>().Property("Ticks");
				case BinFormat.OLEDate:
					return value.Cast<DateTime>().MethodInvoke("ToOADate");
				case BinFormat.FileTime:
					return value.Cast<DateTime>().MethodInvoke("ToFileTimeUtc");
				case BinFormat.PString:
					switch (fromType)
					{
						case "string":
						case "String":
						case "System.String":
							return value.MethodInvoke("ToCharArray");
						default:
							return value.Cast(toType.AsType());
					}
				default:
					return value.Cast(toType.AsType());
			}
		}

		#endregion
	}
}
